import { isObject } from '@/utils/common';

/**
 * 分批打包请求
 * @param { object } options.source 数据源对象，包含大数据字段
 * @param { Array<string> } options.keys 需要分包处理的 key，注意：key 必须要在 options.source 源对象中存在
 * @param { function } options.request 请求方法体
 * @param { object } [options.extraParams] 请求接口需要传递的参数，可以为空
 * @param { number } options.maxNum 每个包最多存放的数量
 * @return { Array<{ request, params }> }
 *
 * @example
 *  分批传输队列
 *  const requestQueue = batchPackageRequest({
 *    source: { equipmentList, facilitiesList },
 *    keys: ['equipmentList', 'facilitiesList'],
 *    request: this.$api.waterZone.zone.saveFacilitiesInfo,
 *    extraParams: { id: res.id },
 *    maxNum: maxCount,
 *  });
 *
 *  并发请求，每次最多发送5个请求
 *  concurRequest(requestQueue, 5).then(values => {
 *    console.log(values);
 *  });
 */
export function batchPackageRequest(options = {}) {
  // 防止共享同一个对象实例
  options = Object.assign({}, options);
  const packages = [];
  if (!isObject(options) || !isObject(options.source) || !Array.isArray(options.keys)) return packages;
  const maxNum = options.maxNum || 1; // 至少存放一个

  // 初始化额外需要传递的参数
  const params = Object.assign({}, options.extraParams || {});
  const obj = {};
  let sum = 0; // 统计所有列表之和
  // 遍历传递的 keys 数组，将所有指定的key存储到对象中
  options.keys.forEach(k => {
    // 判断当前 字段 是否在 source 对象中，只有存在时才初始化
    if (Array.isArray(options.source[k])) {
      params[k] = []; // 初始化每个 key
      obj[k] = JSON.parse(JSON.stringify(options.source[k]));
      sum += obj[k].length;
    }
  });

  const allValidKeys = Object.keys(obj);
  for (let i = 0; i < Math.ceil(sum / maxNum); i++) {
    const batchParams = Object.assign({}, params);
    const currentKey = allValidKeys.find(k => obj[k].length > 0);
    const currentKeyIndex = allValidKeys.findIndex(k => k === currentKey);
    if (currentKey) {
      if (obj[currentKey].length < maxNum) {
        // 将剩余的设备列表全部传输
        batchParams[currentKey] = obj[currentKey].splice(0, obj[currentKey].length);
        // 如果剩余设备数量不足最大传输的数量，则用下一个key的值补齐, 如果已经是最后一个了，则不需要额外处理
        if (batchParams[currentKey].length > 0 && currentKeyIndex < allValidKeys.length - 1) {
          const nextKey = allValidKeys[currentKeyIndex + 1];
          batchParams[nextKey] = obj[nextKey].splice(0, maxNum - batchParams[currentKey].length);
        }
      } else {
        batchParams[currentKey] = obj[currentKey].splice(0, maxNum);
      }
    } else if (currentKeyIndex < allValidKeys.length - 1) { // 如果不是最后一个，则继续处理下一个key
      const nextKey = allValidKeys[currentKeyIndex + 1];
      if (obj[nextKey].length < maxNum) {
        batchParams[nextKey] = obj[nextKey].splice(0, obj[nextKey].length)
      } else {
        batchParams[nextKey] = obj[nextKey].splice(0, maxNum);
      }
    }

    packages.push({ request: options.request, params: batchParams });
  }

  return packages;
}



/**
 * 并发请求
 * @param {Array<{ request: Function, params: any }>} requests 待请求的队列数组
 * @param {number} maxNum 最大并发数, 默认为3次
 *
 * @example
 *   // 使用方式
 *   const requestQueue = [
 *     {
 *       request: api.request, // 当前请求的方法体
 *       params: params, // 当前请求的参数
 *     },
 *     // ...
 *   ];
 *
 *   concurRequest(requestQueue, 5).then(values => {})
 */
function concurRequest(requests, maxNum = 3) {
  return new Promise(resolve => {
    if (requests.length === 0) {
      resolve([]);
      return;
    }
    const results = [];
    let index = 0; // 下一个请求的下标
    let count = 0; // 当前请求完成的数量

    // 发送请求
    async function request() {
      if (index === requests.length) {
        return;
      }
      const i = index; // 存储请求下标，用于记录当前请求完成后，存放在返回的结果数组中对应的位置
      const currentRequest = requests[index];
      index ++;
      try {
        const resp = await currentRequest.request(currentRequest.params);
        // 将当前请求结果加入到 results
        results[i]  = resp;
      } catch (err) {
        // err 加入到 results
        results[i] = err;
      } finally {
        // 判断是否所有的请求都已完成
        count ++;
        if (count === requests.length) {
          resolve(results);
        }
        request();
      }
    }

    // 初始并发请求次数
    const times = Math.min(maxNum, requests.length);
    for (let i = 0; i < times; i++) {
      request();
    }
  });
}
